import service from '@/js/service'
import fxUitls from '@/js/utils'
type RequestType = 'requestPost' | 'requestGet' | 'requestPut' | 'requestDel' | 'requestFile'
export interface RequestConfig {
	responseType?: string
	url?: string
	data?: object
	query?: object
	loading?: boolean
	errorToast?: boolean
	successToast?: boolean
	responseComplete?: boolean
	success?: DataSourceSuccessFunc
	logger?: object
	cache?: boolean
	timeout?: number
	type?: RequestType
}
const caches: { [key: string]: { res: unknown, time: number } } = {}
export const cache = function <T extends { cache: boolean, timeout: number }> (res: T, timeout = 0): T {
	res.cache = true
	res.timeout = timeout
	return res
}
const initConfig = function ({ responseType = 'json', url, data = {}, query = {}, loading, errorToast, successToast, responseComplete, success, logger }: RequestConfig): RequestConfig {
	return {
		responseType,
		url,
		data,
		query,
		loading,
		errorToast,
		successToast,
		responseComplete,
		success,
		logger,
		cache: false,
		timeout: 0
	}
}
export const post = function (config: RequestConfig): RequestConfig {
	const configs = initConfig(config)
	configs.type = 'requestPost'
	return configs
}
export const get = function (config: RequestConfig): RequestConfig {
	const configs = initConfig(config)
	configs.type = 'requestGet'
	return configs
}
export const put = function (config: RequestConfig): RequestConfig {
	const configs = initConfig(config)
	configs.type = 'requestPut'
	return configs
}
export const del = function (config: RequestConfig): RequestConfig {
	const configs = initConfig(config)
	configs.type = 'requestDel'
	return configs
}
export const file = function (config: RequestConfig): RequestConfig {
	const configs = initConfig(config)
	configs.type = 'requestFile'
	return configs
}
const requestHandler = function ({ responseType = 'json', url, data, query, loading, errorToast, successToast, responseComplete, success, logger, cache = false, type = 'requestPost' }: RequestConfig) {
	return new Promise((resolve, reject) => {
		const _service = service[type]
		_service({
			responseType,
			url,
			data,
			query,
			loading,
			errorToast,
			successToast,
			responseComplete,
			success,
			logger
		}).then((res: unknown) => {
			if (cache) {
				const cacheResult = {
					res,
					time: new Date().getTime()
				}
				const key = JSON.stringify({
					url,
					data,
					query,
					type
				})
				caches[key] = cacheResult
			}
			resolve(res)
		}).catch((error: Error) => {
			reject(error)
		})
	})
}
const extendParams = function (pre: unknown, cur: unknown) {
	if (!fxUitls.isDef(cur)) {
		return pre
	}
	if (fxUitls.toRawType(cur) !== 'Object') {
		return cur
	}
	if (fxUitls.toRawType(pre) === 'Object' && !fxUitls.isEmptyObj(pre as object)) {
		return Object.assign({}, cur, pre)
	}
	return cur
}
export const request = function (options: Partial<RequestConfig>, arg: Partial<RequestConfig> = {}): Promise<unknown> {
	const time = new Date().getTime()
	const key = JSON.stringify({
		url: options.url,
		data: options.data,
		query: options.query,
		type: options.type
	})
	const value = caches[key]
	const _options = Object.assign(options, {
		data: extendParams(options.data, arg.data),
		query: extendParams(options.query, arg.query)
	})
	if (fxUitls.isDef(arg.responseType)) {
		_options.responseType = arg.responseType as string
	}
	if (fxUitls.isDef(arg.loading)) {
		_options.loading = arg.loading as boolean
	}
	if (fxUitls.isDef(arg.errorToast)) {
		_options.errorToast = arg.errorToast as boolean
	}
	if (fxUitls.isDef(arg.successToast)) {
		_options.successToast = arg.successToast as boolean
	}
	if (fxUitls.isDef(arg.responseComplete)) {
		_options.responseComplete = arg.responseComplete as boolean
	}
	if (fxUitls.isDef(arg.success)) {
		_options.success = arg.success as DataSourceSuccessFunc
	}
	if (fxUitls.isDef(arg.logger)) {
		_options.logger = arg.logger as object
	}
	if (options.cache) {
		if (!value || (options.timeout && fxUitls.ms2m(time - value.time) > options.timeout)) {
			return requestHandler(_options as RequestConfig)
		} else {
			return Promise.resolve(value.res)
		}
	} else {
		return requestHandler(_options as RequestConfig)
	}
}
export class DataSource {
	options: Partial<RequestConfig>
	arg: Partial<RequestConfig>
	constructor (options = {}, arg = {}) {
		this.options = options
		this.arg = arg
	}

	then (func: DataSourceThenCatchCallback): DataSourceThenCatchReturn {
		return request(this.options, this.arg).then(func)
	}

	catch (func: DataSourceThenCatchCallback): DataSourceThenCatchReturn {
		return request(this.options, this.arg).catch(func)
	}
}
export type DataSourceReturnFunc = {
	(arg?: Partial<RequestConfig>): DataSource
	then (func: DataSourceThenCatchCallback): DataSourceThenCatchReturn
	catch (func: DataSourceThenCatchCallback): DataSourceThenCatchReturn
}

export const dataSource = function (options: Partial<RequestConfig>): DataSourceReturnFunc {
	const func = function (arg: Partial<RequestConfig> = {}) {
		return new DataSource(options, arg)
	}
	func.then = function (func: (value: unknown) => unknown) {
		return request(options).then(func)
	}
	func.catch = function (func: (value: unknown) => unknown) {
		return request(options).catch(func)
	}
	return func
}
